// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library services.hierarchy;

import 'dart:async';
import 'dart:collection';

import 'package:analysis_server/src/services/search/element_visitors.dart';
import 'package:analysis_server/src/services/search/search_engine.dart';
import 'package:analyzer/src/generated/element.dart';

/**
 * Returns direct children of [parent].
 */
List<Element> getChildren(Element parent, [String name]) {
  List<Element> children = <Element>[];
  visitChildren(parent, (Element element) {
    if (name == null || element.displayName == name) {
      children.add(element);
    }
  });
  return children;
}

/**
 * Returns direct non-synthetic children of the given [ClassElement].
 *
 * Includes: fields, accessors and methods.
 * Excludes: constructors and synthetic elements.
 */
List<Element> getClassMembers(ClassElement clazz, [String name]) {
  List<Element> members = <Element>[];
  visitChildren(clazz, (Element element) {
    if (element.isSynthetic) {
      return;
    }
    if (element is ConstructorElement) {
      return;
    }
    if (name != null && element.displayName != name) {
      return;
    }
    if (element is ExecutableElement) {
      members.add(element);
    }
    if (element is FieldElement) {
      members.add(element);
    }
  });
  return members;
}

/**
 * Returns a [Set] with direct subclasses of [seed].
 */
Future<Set<ClassElement>> getDirectSubClasses(
    SearchEngine searchEngine, ClassElement seed) {
  return searchEngine.searchSubtypes(seed).then((List<SearchMatch> matches) {
    Set<ClassElement> subClasses = new HashSet<ClassElement>();
    for (SearchMatch match in matches) {
      ClassElement subClass = match.element;
      subClasses.add(subClass);
    }
    return subClasses;
  });
}

/**
 * @return all implementations of the given {@link ClassMemberElement} is its superclasses and
 *         their subclasses.
 */
Future<Set<ClassMemberElement>> getHierarchyMembers(
    SearchEngine searchEngine, ClassMemberElement member) {
  Set<ClassMemberElement> result = new HashSet<ClassMemberElement>();
  // constructor
  if (member is ConstructorElement) {
    result.add(member);
    return new Future.value(result);
  }
  // method, field, etc
  String name = member.displayName;
  ClassElement memberClass = member.enclosingElement;
  List<Future> futures = <Future>[];
  Set<ClassElement> searchClasses = getSuperClasses(memberClass);
  searchClasses.add(memberClass);
  for (ClassElement superClass in searchClasses) {
    // ignore if super- class does not declare member
    if (getClassMembers(superClass, name).isEmpty) {
      continue;
    }
    // check all sub- classes
    var subClassFuture = getSubClasses(searchEngine, superClass);
    var membersFuture = subClassFuture.then((Set<ClassElement> subClasses) {
      subClasses.add(superClass);
      for (ClassElement subClass in subClasses) {
        List<Element> subClassMembers = getChildren(subClass, name);
        for (Element member in subClassMembers) {
          if (member is ClassMemberElement) {
            result.add(member);
          }
        }
      }
    });
    futures.add(membersFuture);
  }
  return Future.wait(futures).then((_) {
    return result;
  });
}

/**
 * Returns non-synthetic members of the given [ClassElement] and its super
 * classes.
 *
 * Includes: fields, accessors and methods.
 * Excludes: constructors and synthetic elements.
 */
List<Element> getMembers(ClassElement clazz) {
  List<Element> members = <Element>[];
  members.addAll(getClassMembers(clazz));
  Set<ClassElement> superClasses = getSuperClasses(clazz);
  for (ClassElement superClass in superClasses) {
    members.addAll(getClassMembers(superClass));
  }
  return members;
}

/**
 * Returns a [Set] with all direct and indirect subclasses of [seed].
 */
Future<Set<ClassElement>> getSubClasses(
    SearchEngine searchEngine, ClassElement seed) {
  Set<ClassElement> subs = new HashSet<ClassElement>();
  // prepare queue
  List<ClassElement> queue = new List<ClassElement>();
  queue.add(seed);
  // schedule subclasss search
  addSubClasses() {
    // add direct subclasses of the next class
    while (queue.isNotEmpty) {
      ClassElement clazz = queue.removeLast();
      if (subs.add(clazz)) {
        return getDirectSubClasses(searchEngine, clazz).then((directSubs) {
          queue.addAll(directSubs);
          return new Future(addSubClasses);
        });
      }
    }
    // done
    subs.remove(seed);
    return subs;
  }
  return new Future(addSubClasses);
}

/**
 * Returns a [Set] with all direct and indirect superclasses of [seed].
 */
Set<ClassElement> getSuperClasses(ClassElement seed) {
  Set<ClassElement> result = new HashSet<ClassElement>();
  // prepare queue
  List<ClassElement> queue = new List<ClassElement>();
  queue.add(seed);
  // process queue
  while (!queue.isEmpty) {
    ClassElement current = queue.removeLast();
    // add if not checked already
    if (!result.add(current)) {
      continue;
    }
    // append supertype
    {
      InterfaceType superType = current.supertype;
      if (superType != null) {
        queue.add(superType.element);
      }
    }
    // append interfaces
    for (InterfaceType intf in current.interfaces) {
      queue.add(intf.element);
    }
  }
  // we don't need "seed" itself
  result.remove(seed);
  return result;
}

/**
 * If the given [element] is a synthetic [PropertyAccessorElement] returns
 * its variable, otherwise returns [element].
 */
Element getSyntheticAccessorVariable(Element element) {
  if (element is PropertyAccessorElement) {
    if (element.isSynthetic) {
      return element.variable;
    }
  }
  return element;
}
